package ru.papergames.battleserver.model.battle.turnmanager;

import org.apache.log4j.Logger;
import ru.papergames.Config;
import ru.papergames.battleserver.model.battle.AbstractBattle;
import ru.papergames.battleserver.model.battle.BattleManager;
import ru.papergames.battleserver.model.battle.BattleState;

/**
 * @author: ATroshanin
 * Date: 28.04.2010
 * Time: 17:07:11
 */
class TurnControllerThreadUpgr extends Thread {

    private static Logger log = Logger.getLogger(TurnControllerThreadUpgr.class.getName());

    public static final int SLEEP_TIME = Config.MILLIS_IN_TICK * 2;

    private final BattleHolderUpgr header;
    private BattleHolderUpgr tail;
    private final Object tailLock = new Object();

    private volatile boolean shuttingDown;
    private volatile boolean active;
    private int activeBattleCnt;

    private final Object lock = new Object();

    /**
     * Statistic varaibles
     */
    private long minDelta;
    private long maxDelta;
    private long avgDelta;
    private int counter;

    TurnControllerThreadUpgr(int num) {
        activeBattleCnt = 0;
        setName("TurnControllerThreadUpgr[" + num + "]");

        header = new BattleHolderUpgr();
        tail = header;
    }

    boolean addBattleToTurnProcess(AbstractBattle battle) {
        if (++activeBattleCnt > Config.BATTLE_COUNT_IN_ONE_TURN_CONTROLLER) {
            activeBattleCnt--;
            return false;
        }

        BattleHolderUpgr battleHolderUpgr = new BattleHolderUpgr();
        battleHolderUpgr.setBattle(battle);

        synchronized (tailLock) {
            battleHolderUpgr.previous = tail;
            tail.next = battleHolderUpgr; // after executing this line, - battle holder is in processing!!!
            tail = battleHolderUpgr;
        }
        activateIfNotActive();
        return true;
    }

    public void run() {
        log.info("TurnControllerThread.run. Start turn controller thread");
        initStatisticVars();
        while(!shuttingDown) {
            waitIfNotActive();
            if (!shuttingDown) {
                long start = System.currentTimeMillis();
                processBattles();
                long delta = System.currentTimeMillis() - start;
                processStatistic(delta);
                if (delta > SLEEP_TIME - 1) {
                    log.warn("ALARM!!!! - battles were processed in TurnController more than [" +
                            (SLEEP_TIME  - 1) + "ms]. process time: " + delta);
                    delta = SLEEP_TIME  - 1;
                }

                sleepAfterProcess(SLEEP_TIME - delta);
            }
        }
        log.info("TurnControllerThread.run. FINISH turn controller thread");
    }

    private void processBattles() {

        try {
            BattleHolderUpgr battleHolderUpg = header;
            while((battleHolderUpg = battleHolderUpg.next) != null) {
                try {
                    processBattleHolder(battleHolderUpg);
                    battleHolderUpg.errorsCnt = 0;
                } catch (Exception e) {
                    battleHolderUpg.errorsCnt++;
                    if (battleHolderUpg.errorsCnt > 10) {
                        // removeing battle
                        removeBattleHolder(battleHolderUpg);
                    }
                    e.printStackTrace();
                }
            } // end while loop
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private void processBattleHolder(BattleHolderUpgr battleHolderUpg) {
        AbstractBattle battle = battleHolderUpg.getBattle();
        if (battle == null || battle.isBattleToBeRemoved()) {
            removeBattleHolder(battleHolderUpg);
            return;
        }
        switch (battle.getBattleState()) {
            case CLIENT_SYNCHRONIZATION_AFTER_START: {
                battle.checkAllClientsLoadBattle();
                break;
            }
            case RUNNING: {
                if (!battle.isBattleTurnProceeds()) {
                    if (battle.getActionLock().tryLock()) {
                        try {
                            if (!battle.isBattleTurnProceeds()) {
                                battle.finishPlayerTurn();
                                if (battle.choseNextPlayerForTurn()) {
                                    battle.startCurrentTurn();
                                }
                            }
                        } finally {
                            battle.getActionLock().unlock();
                        }
                    }
                }
                break;
            }
            case FINISHED: {
                battle.setBattleState(BattleState.TO_BE_REMOVED);
                break;
            }
        } // end swtich
    }

    /**
     * We have to synchronize removing battleHolder if it is TAIL,
     * because TAIL could be accessed from other threads(threads which add new battles)
     * All other holders are accessed only from current thread, so we can manage them as we want.
     * @param battleHolderUpg
     */
    private void removeBattleHolder(BattleHolderUpgr battleHolderUpg) {
        if (battleHolderUpg.next == null) { // If yes, then battleHolderUpg is tail!!!
            synchronized (tailLock) {
                if (battleHolderUpg.next == null) {
                    battleHolderUpg.previous.next = null;
                    tail = battleHolderUpg.previous;
                } else {
                    battleHolderUpg.previous.next = battleHolderUpg.next;
                    battleHolderUpg.next.previous = battleHolderUpg.previous;
                }
            }
        } else {
            battleHolderUpg.previous.next = battleHolderUpg.next;
            battleHolderUpg.next.previous = battleHolderUpg.previous;
        }
        activeBattleCnt--;
        activeBattleCnt = activeBattleCnt <= 0 ? 0 : activeBattleCnt;
        active = activeBattleCnt != 0;
        BattleManager.getInstance().removeBattle(battleHolderUpg.getBattle());
    }

    private void waitIfNotActive() {
        if (!active) {
            synchronized (lock) {
                if (!active && activeBattleCnt == 0) {
                    try {
                        log.info("TurnControllerThread.waitIfNotActive. Start waiting");
                        lock.wait();
                        log.info("TurnControllerThread.waitIfNotActive. Got battles. Starting to work again.");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    private void activateIfNotActive() {
        if (!active) {
            synchronized (lock) {
                active = true;
                lock.notifyAll();
            }
        }
    }

    private void sleepAfterProcess(long millis) {
        try {
            sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void initStatisticVars() {
        minDelta = 100000;
        maxDelta = -1;
        avgDelta = 0;
        counter = 0;
    }

    private void processStatistic(long delta) {
        minDelta = (delta < minDelta) ? delta : minDelta;
        maxDelta = (delta > maxDelta) ? delta : maxDelta;
        avgDelta += delta;
        counter++;
        if (counter >= 50) {
            avgDelta = (avgDelta - minDelta - maxDelta) / counter;
            log.info("TurnControllerThreadUpgr: DELTA INFO(activeBattleCnt[" + activeBattleCnt + "]): min[" + minDelta +"], max[" + maxDelta + "], avg[" + avgDelta + "]");
            initStatisticVars();
        }
        //log.info("delta:" + delta);
    }

    void shutDown() {
        this.shuttingDown = true;
        synchronized (lock) {
            lock.notify();
        }
    }

    int getActiveBattleCnt() {
        return activeBattleCnt;
    }

    boolean isFull() {
        return activeBattleCnt == Config.BATTLE_COUNT_IN_ONE_TURN_CONTROLLER;
    }
}
